<?php

namespace chleniang\ClsToken;


use chleniang\ClsToken\exception\ClsTokenEmptyException;
use chleniang\ClsToken\exception\ClsTokenException;
use chleniang\ClsToken\exception\ClsTokenUserIdentifierEmptyException;
use think\App;
use think\helper\Arr;
use think\Manager;

/**
 * Token具体实现类
 */
class Token extends Manager
{
    // 各个Driver的命名空间
    protected $namespace = "\\chleniang\\ClsToken\\driver\\";

    private static string $default_secret_key = 'tk_sec_key';

    public function __construct(App $app)
    {
        if (is_null($app)) {
            $app = app();
        }
        parent::__construct($app);
    }


    public function getDefaultDriver()
    {
        return $this->getConfig('default');
    }

    /**
     * @param string|null $name
     * @param mixed|null  $default
     *
     * @return array|mixed
     */
    public function getConfig(
        string|null $name = null,
        mixed       $default = null
    )
    {
        if (!is_null($name)) {
            return $this->app->config
                ->get('cls_token.' . $name, $default);
        }
        return $this->app->config->get('cls_token');
    }


    /**
     * 获取存储类型驱动实例 或 切换存储类型
     *
     * @param string|null $storeKey 存储标识(默认null:使用配置中的默认配置项)
     *
     * @return Driver
     *
     */
    public function store(string|null $storeKey = null)
    {
        return $this->driver($storeKey);
    }


    /**
     * 生成Token
     *      =登录成功后生成token记录; 含token及refresh_token(如果有的话)
     *
     * @param int|string  $userIdentifier 用户唯一标识(ID/UUID)
     * @param array       $userExInfo     token记录中要保存的用户其他信息(不要太多)
     * @param string|null $storeKey       存储标识(默认null:使用配置中的默认配置项)
     *
     * @return array
     * @throws ClsTokenException
     */
    public function buildToken(
        int|string  $userIdentifier,
        array       $userExInfo = [],
        string|null $storeKey = null
    )
    {
        if (empty($userIdentifier)) {
            throw new ClsTokenUserIdentifierEmptyException();
        }

        return $this->store($storeKey)
            ->buildToken($userIdentifier, $userExInfo);
    }

    /**
     * 通过 refresh_token 及 用户标识 更新 token
     *
     * @param string      $refreshToken   刷新令牌
     * @param int|string  $userIdentifier 用户唯一标识(id/uuid)
     * @param string|null $storeKey       存储标识(默认null:使用配置中的默认配置项)
     *
     * @throws ClsTokenException
     */
    public function updateToken(
        string      $refreshToken,
        int|string  $userIdentifier,
        string|null $storeKey = null
    ): array
    {
        if (empty($refreshToken)) {
            throw new ClsTokenEmptyException();
        }
        if (empty($userIdentifier)) {
            throw new ClsTokenUserIdentifierEmptyException();
        }

        return $this->store($storeKey)
            ->updateToken($refreshToken, $userIdentifier);
    }


    /**
     * 验证token有效性
     *      校验通过返回true,否则抛出异常
     *
     * @param string      $token          待验证的令牌字符串
     * @param int|string  $userIdentifier 用户唯一标识(ID/UUID)
     * @param string      $checkType      待校验令牌类型 "access"(默认) / "refresh"
     * @param string|null $storeKey       存储标识(默认null:使用配置中的默认配置项)
     *
     * @return bool
     * @throws ClsTokenEmptyException
     * @throws ClsTokenException
     * @throws ClsTokenUserIdentifierEmptyException
     */
    public function check(
        string      $token,
        int|string  $userIdentifier,
        string      $checkType = TokenConstant::CHECK_TYPE_ACCESS,
        string|null $storeKey = null
    ): bool
    {
        if (empty($token)) {
            throw new ClsTokenEmptyException();
        }
        if (empty($userIdentifier)) {
            throw new ClsTokenUserIdentifierEmptyException();
        }

        return $this->store($storeKey)
            ->check($token, $userIdentifier, $checkType);
    }

    /**
     * 获取指定用户标识的token记录;
     *      没有记录返回空数组
     *
     * @param int|string  $userIdentifier 用户标识(id/uuid)
     * @param string|null $storeKey       存储标识(默认null:使用配置中的默认配置项)
     *
     * @return array
     * @throws ClsTokenUserIdentifierEmptyException
     */
    public function get(
        int|string  $userIdentifier,
        string|null $storeKey = null
    ): array
    {
        if (empty($userIdentifier)) {
            throw new ClsTokenUserIdentifierEmptyException();
        }

        return $this->store($storeKey)
            ->get($userIdentifier);
    }

    /**
     * 删除指定用户标识的token记录
     *      成功删除返回true,没有相关记录返回false
     *
     * @param int|string  $userIdentifier 用户标识(id/uuid)
     * @param string|null $storeKey       存储标识(默认null:使用配置中的默认配置项)
     *
     * @return bool
     * @throws ClsTokenException
     */
    public function delete(
        int|string  $userIdentifier,
        string|null $storeKey = null
    ): bool
    {
        if (empty($userIdentifier)) {
            throw new ClsTokenUserIdentifierEmptyException();
        }
        return $this->store($storeKey)
            ->delete($userIdentifier);
    }

    /**
     * 获取驱动配置
     *
     * @param string      $storeKey 存储标识
     * @param string|null $name     配置项名称
     * @param mixed|null  $default  默认值
     *
     * @return array
     */
    protected function getStoreConfig(
        string $storeKey,
        string $name = null,
        mixed  $default = null
    )
    {
        if ($config = $this->getConfig("stores.{$storeKey}")) {
            return Arr::get($config, $name, $default);
        }

        throw new \InvalidArgumentException("Store-Key [$storeKey] not found.");
    }

    protected function resolveType(string $storeKey)
    {
        return $this->getStoreConfig($storeKey, 'type', 'redis');
    }

    /**
     * 获取驱动配置(指定存储标识的配置) 及 基础配置(除"default","stores"之外的所有配置);
     *
     * @param string $storeKey 存储标识
     *
     * @return mixed
     */
    protected function resolveConfig(string $storeKey)
    {
        $allConf = $this->getConfig();

        if (!isset($allConf['stores'][$storeKey])) {
            throw new \InvalidArgumentException("Store-Key [$storeKey] not found.");
        }

        // 驱动配置
        $resultConf['driver'] = $allConf['stores'][$storeKey];

        // 基础配置
        $baseOptions = $allConf;
        Arr::forget($baseOptions, ['default', 'stores']);
        $resultConf['base'] = $baseOptions;

        return $resultConf;
    }

    /**
     * 获取存储驱动对象
     *
     * @param string $storeKey 存储标识
     *
     * @return mixed
     */
    protected function getDriver(string $storeKey)
    {
        // 原父类方法先找驱动名称数组,有就返回
        // 如果连续使用同一驱动对象操作,database类型驱动返回的对象是同一个，会将前后where条件全部拼接起来
        // return $this->drivers[$name] ?? $this->createDriver($name);

        $driverType = $this->getStoreConfig($storeKey, 'type');
        if (strtolower($driverType) === 'database') {
            return $this->createDriver($storeKey);
        }
        return $this->drivers[$storeKey] ?? $this->createDriver($storeKey);
    }

}
